/*-
 * Generates code for the methods.
 *
 * Copyright (c) 2010 Alex Suhan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer
 * in this position and unchanged.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.util.Map;

class CodeSection
{
    LayoutBuilder ctx;
    int crtRegIdx;
    String crtClassName;
    String crtMethodName;
    ScalaMethodInfo crtMethodInfo;
    private int dispatchLabelIdx;
    private int iteLabelIdx;
    private int loopLabelIdx;
    
    private static String void_disp_handler = "void_disp_handler";
    
    CodeSection(LayoutBuilder ctx)
    {
        this.ctx = ctx;
    }
    
    // these are helpers for generating unique labels per code section and
    // could move to 'ScalaMethodInfo' class with and used with method name
    // as a prefix; no biggie, though
    int getUniqueIteLabelIdx() {
        return iteLabelIdx++;
    }
    
    int getUniqueDispatchLabelIdx() {
        return dispatchLabelIdx++;
    }
    
    int getUniqueLoopIdx() {
        return loopLabelIdx++;
    }
    
    // this is a simple wrapper over the built-in library dispatch abort handler
    // which does integer boxing on the second parameter
    private static void emitDispatchToVoidHandler(java.io.PrintStream printer) {
        
        printer.println(".function \"Void dispatch handler\", 2, 0");
        printer.print(void_disp_handler + ":\n");
        
        String  temp1 = CgenSupport.VR(0);
        String  temp2 = CgenSupport.VR(1);
        String  temp3 = CgenSupport.VR(2);
        
        CgenSupport.emitIRMov(temp1, "VI0", printer);
        CgenSupport.emitIRMov(temp3, "VI1", printer);
        CgenSupport.emitBoxInt(temp3, temp2, printer);
        
        CgenSupport.emitIRMov("VI0", temp1, printer);
        CgenSupport.emitIRMov("VI1", temp2, printer);
        
        CgenSupport.emitIRCall(CgenSupport._dispatch_abort, printer);
        
        printer.println("\treturn");
        printer.println(".end\n");
    }
    
    // emits the initialization methods which properly assign values to
    // the attributes of a newly-created instance; the names are required
    // by the CoolIR runtime and further by the Cool runtime for SPIM - this
    // Cool-influenced approach should vanish soon with a new intermediate
    // language and runtime
    private void emitInitMethods(java.io.PrintStream printer) {
        for (String className: ScalaMapsBuilder.classTable.keySet()) {
            crtClassName = className;
            if (LayoutBuilder.isRuntimeClass(className)) {
                printer.println(IRLingo.methodTag + " \"" + className + " init code\", 1, 0");
                printer.println(className + "_init:");
                printer.println("\treturn");
                printer.println(".end\n");
            } else {
                emitInitMethod(ctx.getClassLayout(className),
                               ScalaMapsBuilder.classTable.get(className),
                               printer);
            }
        }
    }
    
    // emits the init method for a class with layout information stored
    // into 'classLayout' and AST information stored into 'classInfo'
    private void emitInitMethod(ScalaClassLayout classLayout,
                                ScalaClassInfo classInfo,
                                java.io.PrintStream printer) {
        
        printer.println(IRLingo.methodTag + " \"" + classLayout.name + " init code\", 1, 0");
        printer.println(classLayout.name + "_init:");
        
        printer.println("\t## store self into the reserved VR0");
        CgenSupport.emitIRMov(CgenSupport.IR_SELF, "VI0", printer);
        
        // call the base constructor before anything else
        ScalaClassLayout parentLayout = classLayout.parent;
        if (!parentLayout.name.equals(ScalaConstants.NoTypeClassname))
            CgenSupport.emitIRCall(parentLayout.name + "_init", printer);
        
        // reorder attributes from the map by offset and therefore by position
        // into source code; the initialization order is significant
        ScalaClassLayout.CgenAttributeInfo[] attrByIdx =
            new ScalaClassLayout.CgenAttributeInfo[classLayout.attrOffset.size()];
        
        for (Map.Entry<String, Integer> attrInfo: classLayout.attrOffset.entrySet())
            attrByIdx[attrInfo.getValue()] =
                new ScalaClassLayout.CgenAttributeInfo(attrInfo.getKey(), attrInfo.getValue());
        
        for (ScalaClassLayout.CgenAttributeInfo attrInfo: attrByIdx) {
            // avoid duplicate initialization; note that each class stores
            // inherited attribute info along with own attribute info, so skip
            // initialization for the attributes from the base class
            // TODO: find out what to do with the redefined attributes from the
            // Scala specification
            if (parentLayout.attrOffset.containsKey(attrInfo.name))
                continue;
            
            ScalaAttrInfo astAttrInfo = classInfo.getAttribute(attrInfo.name);
            // if an initialization expression appears in the source code,
            // generate code for it and assign it
            if (astAttrInfo.getInit() != null) {
                ExpressionCode.code(new ScalaAssign(attrInfo.name, IdScope.CLASS, astAttrInfo.getType(), astAttrInfo.getInit()), this);
                CgenSupport.emitPopRegStack(1, this);
            // Integer, Boolean and String types receive a default meaningful
            // value instead of ordinary null reference; because of integer
            // auto boxing / unboxing feature, the codepath is different only
            // for String; again, check with the Scala specification
            } else if (astAttrInfo.getType().equals(ScalaConstants.StringClassname)) {
                ExpressionCode.code(new ScalaNew(ScalaConstants.StringClassname), this);
                CgenSupport.emitIRStore("VR1", attrInfo.offset, CgenSupport.IR_SELF, printer);
                CgenSupport.emitPopRegStack(1, this);
            } else {
                CgenSupport.emitIRMov("VR1", "0", printer);
                CgenSupport.emitIRStore("VR1", attrInfo.offset, CgenSupport.IR_SELF, printer);
            }
        }
        
        CgenSupport.emitIRMov("VI0", CgenSupport.IR_SELF, printer);

        printer.println("\treturn");
        printer.println(".end\n");
    }
    
    void code(java.io.PrintStream printer)
    {
        ExpressionCode.printer = printer;
        
        printer.println("#### code section");
        printer.println("\t.code\n");
        
        emitDispatchToVoidHandler(printer);
        emitInitMethods(printer);
        
        for (String className: ScalaMapsBuilder.classTable.keySet()) {
            ScalaClassInfo classInfo = ScalaMapsBuilder.classTable.get(className);
            if (LayoutBuilder.isRuntimeClass(className))
                continue;
            crtClassName = className;
            ScalaClassInfo crtClassInfo =
                ScalaMapsBuilder.classTable.get(className);
            for (String methodName: classInfo.getMethodNames()) {
                crtMethodName = methodName;
                crtMethodInfo = crtClassInfo.getMethod(methodName);
                String fqn = className + "." + methodName;
                printer.print(IRLingo.methodTag + " \"" + fqn + "\", ");
                ScalaMethodInfo methodInfo = classInfo.getMethod(methodName);
                printer.println(methodInfo.getFormalParams().size() + 1 + ", 1");
                printer.println(fqn + ":");
                crtRegIdx = 0;
                printer.println("\t## store self and arguments into the reserved VR0..VR" + methodInfo.getFormalParams().size());
                for (int paramIdx = 0; paramIdx < methodInfo.getFormalParams().size() + 1; paramIdx++) {
                    int regIdx = paramIdx;
                    CgenSupport.emitIRMov("VR" + regIdx, "VI" + regIdx, printer);
                    CgenSupport.emitPushRegStack(1, this);
                }
                // self register is never used
                this.crtRegIdx--;
                for (String localVarName: methodInfo.localVars.keySet()) {
                    ScalaClassLayout classLayout =
                        ctx.getClassLayout(crtClassName);
                    ScalaClassLayout.CgenMethodInfo methodLayout =
                        classLayout.methodOffset.get(methodName);
                    methodLayout.putLocalVar(localVarName, crtRegIdx);
                    CgenSupport.emitPushRegStack(1, this);
                }
                
                ExpressionCode.code(methodInfo.body, this);
                
                if (methodInfo.body.typeName().equals(ScalaConstants.IntegerClassname) && !methodInfo.getReturnedType().equals(ScalaConstants.IntegerClassname)) {
                    String retReg = "VR" + (this.crtRegIdx - 1 + CgenSupport.firstIRReg);
                    String tempReg = "VR" + (this.crtRegIdx + CgenSupport.firstIRReg);

                    CgenSupport.emitBoxInt(retReg, tempReg, printer);
                    CgenSupport.emitIRMov(retReg, tempReg, printer);
                }

                printer.println("\t## store result into the output register");

                CgenSupport.emitIRMov("VI0", CgenSupport.VR(this.crtRegIdx - 1), printer);
                printer.println("\treturn");
                printer.println(".end\n");
            }
        }
    }
}
